﻿using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;

namespace ITSM.Filter
{
    public enum TokenType
    {
        Ok,
        Fail,
        Expired
    }
    public class TokenHelper : ITokenHelper
    {
        private readonly IOptions<JWTConfig> _options;
        public TokenHelper()
        {
            JWTConfig jWTConfig = new()
            {
                Issuer = "SYS",
                Audience = "USER",
                IssuerSigningKey = "So@92Roter_IDX10653",
                AccessTokenExpiresMinutes = 60 * 12
            };
            _options = Options.Create(jWTConfig);
        }
        public TnToken CreateToken(string str)
        {
            //携带的负载部分，类似一个键值对
            List<Claim> claims = new()
            {
                new Claim("Id", str)
            };
            //创建token
            return CreateTokenString(claims);
        }
        public TnToken CreateTokenNoDate(string str)
        {
            //携带的负载部分，类似一个键值对
            List<Claim> claims = new()
            {
                new Claim("Id", str)
            };
            //创建token
            return CreateTokenStringNoDate(claims);
        }

        public TnToken CreateToken<T>(T user) where T : class
        {
            //携带的负载部分，类似一个键值对
            List<Claim> claims = new();
            //这里我们用反射把model数据提供给它
            foreach (var item in user.GetType().GetProperties())
            {
                object? obj = item.GetValue(user);
                string value = "";
                if (obj != null)
                {
#pragma warning disable CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。
                    value = obj.ToString();
#pragma warning restore CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。
                }
#pragma warning disable CS8604 // 引用类型参数可能为 null。
                claims.Add(new Claim(item.Name, value));
#pragma warning restore CS8604 // 引用类型参数可能为 null。
            }
            //创建token
            return CreateTokenString(claims);
        }
        /// <summary>
        /// 根据键值对提供负载生成token
        /// </summary>
        /// <param name="keyValuePairs"></param>
        /// <returns></returns>
        public TnToken CreateToken(Dictionary<string, string> keyValuePairs)
        {
            //携带的负载部分，类似一个键值对
            List<Claim> claims = new();
            //这里我们通过键值对把数据提供给它
            foreach (var item in keyValuePairs)
            {
                claims.Add(new Claim(item.Key, item.Value));
            }
            //创建token
            return CreateTokenString(claims);
        }
        /// <summary>
        /// 生成token
        /// </summary>
        /// <param name="claims">List的 Claim对象</param>
        /// <returns></returns>
        public TnToken CreateTokenString(List<Claim> claims)
        {
            var now = DateTime.Now;
            var expires = now.Add(TimeSpan.FromMinutes(_options.Value.AccessTokenExpiresMinutes));
            var token = new JwtSecurityToken(
                issuer: _options.Value.Issuer, //Token发布者
                audience: _options.Value.Audience,//Token接受者
                claims: claims,//携带的负载
                notBefore: now,//当前时间token生成时间
                expires: expires,//过期时间
                signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.Value.IssuerSigningKey)), SecurityAlgorithms.HmacSha256)
                );
            string tokenStr = new JwtSecurityTokenHandler().WriteToken(token);

            return new TnToken { TokenStr = tokenStr, Expires = expires };
        }
        /// <summary>
        /// 生成token
        /// </summary>
        /// <param name="claims">List的 Claim对象</param>
        /// <returns></returns>
        public TnToken CreateTokenStringNoDate(List<Claim> claims)
        {
            var now = DateTime.Today;
            var expires = now.Add(TimeSpan.FromMinutes(_options.Value.AccessTokenExpiresMinutes));
            var token = new JwtSecurityToken(
                issuer: _options.Value.Issuer, //Token发布者
                audience: _options.Value.Audience,//Token接受者
                claims: claims,//携带的负载
                notBefore: now,//当前时间token生成时间
                expires: expires,//过期时间
                signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.Value.IssuerSigningKey)), SecurityAlgorithms.HmacSha256)
                );
            string tokenStr = new JwtSecurityTokenHandler().WriteToken(token);

            return new TnToken { TokenStr = tokenStr, Expires = expires };
        }

        /// <summary>
        /// 验证身份 验证签名的有效性
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <param name="validatePayLoad">自定义各类验证； 是否包含那种申明，或者申明的值， </param>
        public bool ValiToken(string encodeJwt, Func<Dictionary<string, string>, bool>? validatePayLoad)
        {
            try
            {
                var success = true;
                var jwtArr = encodeJwt.Split('.');
                if (jwtArr.Length < 3)//数据格式都不对直接pass
                {
                    return false;
                }
                var header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0]));
                var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1]));
                //配置文件中取出来的签名秘钥
                var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(_options.Value.IssuerSigningKey));
                //验证签名是否正确（把用户传递的签名部分取出来和服务器生成的签名匹配即可）
                success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))));
                if (!success)
                {
                    return success;//签名不正确直接返回
                }

                //其次验证是否在有效期内（也应该必须）
                var now = ToUnixEpochDate(DateTime.UtcNow);
                success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString()));
                if (!success)
                {
                    return success;//签名不正确直接返回
                }

                //不需要自定义验证不传或者传递null即可
                if (validatePayLoad == null)
                    return true;

                //再其次 进行自定义的验证
                success = success && validatePayLoad(payLoad);

                return success;

            }
            catch (Exception)
            {
                return false;
            }
        }
        /// <summary>
        /// 验证身份 验证签名的有效性,不校验时间
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <param name="validatePayLoad">自定义各类验证； 是否包含那种申明，或者申明的值， </param>
        public bool ValiTokenNoDate(string encodeJwt, Func<Dictionary<string, string>, bool>? validatePayLoad)
        {
            try
            {
                var success = true;
                var jwtArr = encodeJwt.Split('.');
                if (jwtArr.Length < 3)//数据格式都不对直接pass
                {
                    return false;
                }
                var header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0]));
                var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1]));
                //配置文件中取出来的签名秘钥
                var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(_options.Value.IssuerSigningKey));
                //验证签名是否正确（把用户传递的签名部分取出来和服务器生成的签名匹配即可）
                success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))));
                if (!success)
                {
                    return success;//签名不正确直接返回
                }

                //不需要自定义验证不传或者传递null即可
                if (validatePayLoad == null)
                    return true;

                //再其次 进行自定义的验证
                success = success && validatePayLoad(payLoad);

                return success;

            }
            catch (Exception)
            {
                return false;
            }
        }
        public string ValidateToken(string encodeJwt)
        {
            var validationParameters = new TokenValidationParameters()
            {
                ValidIssuer = _options.Value.Issuer,
                ValidAudience = _options.Value.Audience,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.Value.IssuerSigningKey))
            };
            var principal = new JwtSecurityTokenHandler().ValidateToken(encodeJwt, validationParameters, out _);
            var id = principal.Claims.First().Value;
            return id;
        }
        /// <summary>
        /// 时间转换
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        private long ToUnixEpochDate(DateTime date)
        {
            return (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <param name="validatePayLoad"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public TokenType ValiTokenState(string encodeJwt, Func<Dictionary<string, string>, bool> validatePayLoad, Action<Dictionary<string, string>> action)
        {
            var jwtArr = encodeJwt.Split('.');
            if (jwtArr.Length < 3)//数据格式都不对直接pass
            {
                return TokenType.Fail;
            }
            var header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0]));
            var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1]));
            var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(_options.Value.IssuerSigningKey));
            //验证签名是否正确（把用户传递的签名部分取出来和服务器生成的签名匹配即可）
            if (!string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1]))))))
            {
                return TokenType.Fail;
            }
            //其次验证是否在有效期内（必须验证）
            var now = ToUnixEpochDate(DateTime.UtcNow);
            if (!(now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString())))
            {
                return TokenType.Expired;
            }

            //不需要自定义验证不传或者传递null即可
            if (validatePayLoad == null)
            {
                action(payLoad);
                return TokenType.Ok;
            }
            //再其次 进行自定义的验证
            if (!validatePayLoad(payLoad))
            {
                return TokenType.Fail;
            }
            //可能需要获取jwt摘要里边的数据，封装一下方便使用
            action(payLoad);
            return TokenType.Ok;
        }
        private string diyTokenStr(string tokenStr)
        {
            string firstStr = tokenStr.Substring(tokenStr.IndexOf("."), 4);
            string lastStr = tokenStr.Substring(tokenStr.LastIndexOf("."), 4);

            string tokenStr1 = tokenStr[..(tokenStr.IndexOf(".") + 4)];
            string tokenStr2 = tokenStr[(tokenStr.IndexOf(".") + 4)..];

            tokenStr1 = tokenStr1.Replace(firstStr, lastStr);
            tokenStr2 = tokenStr2.Replace(lastStr, firstStr);

            return tokenStr1 + tokenStr2;
        }
    }
}
